/* LinePasteboard.cpp
Copyright (c) 2008 Michael Zahniser
Please note the license terms (MIT license) at the end of this document.

Function definitions for the Linux implementation of the Pasteboard object.

This uses X11, so you must link with -lX11. But that's much better than pulling
in all of GTK.
*/

#include "Pasteboard.h"

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <sys/wait.h>

namespace {
	// Private functions:
	
	// Wait for someone to respond with the clipboard contents.
	string WaitForConversion(Display *display, Atom selection)
	{
		string result;

		XEvent event;
		Atom target;
		int format;
		unsigned long length;
		unsigned long bytesafter;
		unsigned char *value;
	
		do {
			XNextEvent(display, &event);
		} while(event.type != SelectionNotify || event.xselection.selection != selection);

		XGetWindowProperty(
			event.xselection.display,
			event.xselection.requestor,
			event.xselection.property, 
			0L, 1000000, False, (Atom)AnyPropertyType, 
			&target, &format, &length, &bytesafter, &value);
	
		if(target == XA_STRING)
			result = reinterpret_cast<char *>(value);

		XFree(value);
		
		XDeleteProperty (
			event.xselection.display,
			event.xselection.requestor,
			event.xselection.property);
	
		return result;
	}

	void ProvideSelection(unsigned char *buffer, int length)
	{
		Display *display = XOpenDisplay(NULL);
		if(!display)
			return;

		Window root = XDefaultRootWindow(display);

		int black = BlackPixel(display, DefaultScreen(display));
		Window window = XCreateSimpleWindow(
			display, root, 0, 0, 1, 1, 0, black, black);

		Atom selection = XInternAtom(display, "CLIPBOARD", False);
		Atom targetsAtom = XInternAtom(display, "TARGETS", False);
	
		XSetSelectionOwner(display, selection, window, CurrentTime);
		// Make sure that succeeded. If not, the daemon should bail out.
		Window owner = XGetSelectionOwner(display, selection);

		XEvent event;
		while(owner == window)
		{
			XNextEvent(display, &event);

			if(event.type == SelectionClear && 
					event.xselectionclear.selection == selection)
				break;
			if(event.type != SelectionRequest || 
					event.xselectionrequest.selection != selection)
				continue;

			// This is a request for my selection.
			XSelectionRequestEvent &request = event.xselectionrequest;

			XSelectionEvent response;
			response.type = SelectionNotify;
			response.display = request.display;
			response.requestor = request.requestor;
			response.selection = request.selection;
			response.time = request.time;
			response.target = request.target;
			response.property = request.property;

			if(request.target == XA_STRING)
				XChangeProperty(
					request.display, request.requestor, request.property,
					XA_STRING, 8, PropModeReplace,
					buffer, length);
		
			else if(request.target == targetsAtom)
				XChangeProperty(
					request.display, request.requestor, request.property,
					XA_ATOM, 32, PropModeReplace,
					reinterpret_cast<unsigned char *>(new Atom(XA_ATOM)), 1);

			XSendEvent (display, response.requestor, False,
				0, reinterpret_cast<XEvent *>(&response));
		}
		
		XCloseDisplay(display);
	}
}



const string &Pasteboard::Get()
{
	static string result;
	result.clear();
	
	Display *display = XOpenDisplay(NULL);
	if(display)
	{
		Window root = XDefaultRootWindow(display);
	
		int black = BlackPixel(display, DefaultScreen(display));
		Window window = XCreateSimpleWindow(display, root, 0, 0, 1, 1, 0, black, black);

		Atom selection = XInternAtom(display, "CLIPBOARD", False);
		Atom prop = XInternAtom(display, "XSEL_DATA", False);
		XConvertSelection(display, selection, XA_STRING, prop, window, CurrentTime);
		XSync(display, False);
	
		result = WaitForConversion(display, selection);
	}
	
	return result;
}



void Pasteboard::Set(const string &text)
{
	// Make a local copy of the string.
	unsigned char *buffer = new unsigned char[text.length() + 1];
	text.copy(reinterpret_cast<char *>(buffer), text.length());
	buffer[text.length()] = '\0';
	
	// Fork twice, to create a separate process not related to this one.
	pid_t pid = fork();
	if(pid)
	{
		// We're the parent.
		// If the fork succeeded, wait for the child process to exit.
		// Otherwise, free the buffer.
		if(pid > 0)
		{
			int status;
			waitpid(pid, &status, 0);
		}
		else
			delete [] buffer;
		
		return;
	}

	// We're in the child fork. Spawn a grandchild.
	pid = fork();
	
	// If we're the grandchild...
	if(!pid)
		ProvideSelection(buffer, text.length() + 1);
	
	// If we're the grandchild, or we're the child but the grandchild wasn't
	// created, clean up.
	if(pid <= 0)
		delete [] buffer;
	
	_exit(0);
}



/* Copyright (c) 2008 Michael Zahniser

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE. */
